/*
 * $Id: BaseFileTransferTask.java,v 1.23 2010/07/13 15:50:55 harini Exp $
 * 
 * Copyright (C) 2006 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.dispatcher.tasks;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.database.dao.IFileTransferDetailsDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestDAO;
import com.ge.healthcare.autosc.common.database.dao.IRequestPropertiesDAO;
import com.ge.healthcare.autosc.common.database.dao.ITaskFTConnInfoDAO;
import com.ge.healthcare.autosc.common.database.dao.ITaskFTDetailsDAO;
import com.ge.healthcare.autosc.common.database.dao.ITaskFtRecordDAO;
import com.ge.healthcare.autosc.common.database.pojo.ActFileTransferDetails;
import com.ge.healthcare.autosc.common.database.pojo.ActRequest;
import com.ge.healthcare.autosc.common.database.pojo.ActTask;
import com.ge.healthcare.autosc.common.database.pojo.CfgTaskFtConnInfo;
import com.ge.healthcare.autosc.common.database.pojo.CfgTaskFtDetails;
import com.ge.healthcare.autosc.common.util.ApplicationInfo;
import com.ge.healthcare.autosc.common.util.PropertyConstants;
import com.ge.healthcare.autosc.processor.exception.FileTransferException;

/**
 * @author 212042764
 * 
 */
public abstract class BaseFileTransferTask extends BasicTask implements
		IFileTransferTask {
	private static final String UNEVALUATED = "UNEVALUATED";
	private static final String DOT = ".";
	private static final String FILE_BASE_NAME_IDENTIFIER = "{$FILENAME_BASE}";
	private static final String FILE_EXTENSION_IDENTIFIER = "{$FILENAME_EXT}";
	private static final String FILE_NAME_IDENTIFIER = "{$FILENAME}";
	private static final String TIME_STAMP_IDENTIFIER = "{$Timestamp:[";
	private static final String PROPERTY_IDENTIFIER_START_CLAUSE = "{$Prop:[";
	private static final String REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE = "{$RProp:[";
	private static final String PROPERTY_IDENTIFIER_END_CLAUSE = "]}";
	protected static final String INCOMPLETE_EXT = ApplicationInfo
			.getInstance().getProperty(
					PropertyConstants.FT_INCOMPLETE_EXTENSION, ".incomplete");

	// Class variables
	protected boolean hasFailed = false;
	protected ActTask task = null;
	protected ActRequest request = null;
	protected List<ActFileTransferDetails> ftDetailsLst = null;
	protected List<CfgTaskFtDetails> taskFTDetailsLst = null;
	protected CfgTaskFtConnInfo taskFtConnInfo = null;
	protected Map<Long, Boolean> taskFTRecords = null;
	protected Map<String, String> requestProperties = null;
	protected boolean ignoreFtConnInfo = false;

	// Context variables, to be populated by spring
	protected static IFileTransferDetailsDAO fileTransferDAO = null;
	protected static ITaskFTDetailsDAO ftDetailsDAO = null;
	protected static ITaskFTConnInfoDAO ftConnDetailsDAO = null;
	protected static ITaskFtRecordDAO ftRecordDAO = null;
	protected static IRequestPropertiesDAO requestPropertiesDAO = null;
	protected static IRequestDAO requestDAO = null;

	/**
	 * The class defines the basic execution structure for transferring file.
	 * File Copy, FTP and SFTP will all follow the same structure
	 * 
	 * @see com.ge.healthcare.autosc.processor.dispatcher.tasks.BasicTask#executeTask
	 *      (com.ge.healthcare.autosc.common.database.pojo.ActTask)
	 */
	@Override
	public void executeTask(ActTask actTask) {
		String methodName = "executeTask";
		ASCLogger.debug(this.getClass(), methodName,
				"Received file transfer task for:" + actTask);
		try {
			this.setUp(actTask);
			this.transferFiles();
		} finally {
			this.complete();
		}
		ASCLogger.debug(this.getClass(), methodName,
				"Completed file transfer task for:" + actTask);
	}

	/**
	 * Check if one or more files couldn't be transferred, if yes then throw
	 * FileTransferExceptionso that we retry
	 */
	protected void complete() {
		if (hasFailed) {
			throw new FileTransferException(
					"Failed to transfer one or more of required files",
					PRC_FT_F_FTP_001);
		}
	}

	/**
	 * Loads the required information
	 * 
	 * @param task
	 */
	public void setUp(ActTask task) {
		String methodName = "setUp";
		this.task = task;
		this.request = requestDAO.getByRequestID(task.getRequestId());
		this.loadFilesToTransfer();
		this.loadFtDetails();
		this.loadTaskFtRecords();
		ASCLogger.debug(this.getClass(), methodName,
				"Setup completed for task:" + task);
	}

	/**
	 * Loads the list of files to transfer, we will only load external file list
	 * no internal files need to be transferred In case there are no files to
	 * transfer we will throw exception with failure code
	 */
	protected void loadFilesToTransfer() {
		this.ftDetailsLst = fileTransferDAO
				.getExternalFileTransferLstByRequestId(task.getRequestId());
		if (ASCUtil.isNullOrEmptyList(ftDetailsLst)) {
			throw new FileTransferException(
					"No files available to even try to transfer",
					PRC_FT_F_VAL_003);
		}
	}

	/**
	 * Loads File transfer details, e.g. template and connection info
	 */
	protected void loadFtDetails() {
		this.taskFTDetailsLst = ftDetailsDAO
				.getFileTransferDetailsForTaskType(task.getTaskType());
		if (ASCUtil.isNullOrEmptyList(taskFTDetailsLst)) {
			throw new FileTransferException(
					"No file transfer template defined for task:" + task,
					PRC_FT_F_VAL_001);
		}
		// We don't want to make unnecessary DB Call for File Copy tasks which
		// are going to be a majority for us
		if (!ignoreFtConnInfo) {
			this.taskFtConnInfo = ftConnDetailsDAO
					.getFileConnInfoForTaskType(task.getTaskType());
			if (ASCUtil.isNullOrEmptyObject(taskFtConnInfo)) {
				throw new FileTransferException(
						"Connection details are necessary for non copy file transfer task:"
								+ task, PRC_FT_F_VAL_002);
			}
		}
	}

	/**
	 * Loads the status of each file transfer which might have already been
	 * performed
	 */
	protected void loadTaskFtRecords() {
		taskFTRecords = ftRecordDAO.getFileTransferStatusByTaskId(task
				.getTaskId());
		if (ASCUtil.isNullOrEmptyMap(taskFTRecords)) {
			taskFTRecords = new HashMap<Long, Boolean>();
		}
	}

	/**
	 * Returns the value as specified in ACT_REQUEST_PROPERTIES table
	 * 
	 * @param propertyName
	 * @return
	 */
	protected String getRequestPropertyValue(String propertyName) {
		if (ASCUtil.isNullOrEmptyMap(requestProperties)) {
			this.requestProperties = requestPropertiesDAO
					.getRequestPropertiesByRequestId(request.getRequestId());
		}
		if (ASCUtil.isNullOrEmptyMap(requestProperties)) {
			return null;
		}
		return requestProperties.get(propertyName);
	}

	/**
	 * Marks the request as failed and logs the step log Since process server is
	 * non transactional our saving record here will ensure we don't push file
	 * again on retry
	 * 
	 * @param ftId
	 * @param fileUrl
	 * @param message
	 */
	protected void markFailed(Long ftId, String fileUrl, String message,
			Throwable throwable) {
		String methodName = "markFailed";
		hasFailed = true;
		ASCLogger.error(this.getClass(), methodName, message, throwable, true);
		ftRecordDAO.saveFailure(ftId, task.getTaskId(), fileUrl);
	}
	
	/**
	 * Updates ft record to indicate file was successfully transferred
	 * @param ftId
	 * @param destFile
	 */
	protected void markSuccess(Long ftId, File destFile){
		ftRecordDAO.saveSuccess(ftId, task.getTaskId(), destFile
				.getAbsolutePath());		
	}

	/**
	 * Updates task ft records to state that file has been successfully
	 * transferred
	 * 
	 * @param ftId
	 */
	protected void markTaskFtStatusForSuccess(Long ftId) {
		this.taskFTRecords.put(ftId, true);
	}

	/**
	 * Does the following: 1. Loops through the file name patterns as defined 2.
	 * Loops though all the files and transfers the same
	 */
	protected void transferFiles() {
		String methodName = "transferFiles";
		for (CfgTaskFtDetails ftDetail : this.taskFTDetailsLst) {
			try {
				RE reFilePattern = new RE(ftDetail.getId().getLogFilePattern()
						.trim());
				for (ActFileTransferDetails fileInfo : this.ftDetailsLst) {
					if (ASCUtil.isNullOrEmpty(fileInfo.getFileName())) {
						StringBuffer msg = new StringBuffer().append(
								"Null File name found for file details:")
								.append(fileInfo);
						msg.append(" while executing task:").append(task);
						ASCLogger.error(this.getClass(), methodName, msg
								.toString(), true);
					} else if (taskFTRecords.get(fileInfo.getFtId()) != null
							&& taskFTRecords.get(fileInfo.getFtId())) {
						StringBuffer msg = new StringBuffer()
								.append("File has already been transferred:");
						msg.append(fileInfo).append(" as per task records:")
								.append(taskFTRecords);
						ASCLogger.error(this.getClass(), methodName, msg
								.toString(), true);
					} else if (ftDetail.getDirection().equalsIgnoreCase(fileInfo.getDirection()) &&
							(reFilePattern.match(fileInfo.getFileName()
							.trim()))) {
						this.performTransfer(ftDetail, fileInfo);
					} else {
						StringBuffer msg = new StringBuffer()
								.append("Ignoring file:");
						msg.append(fileInfo.getFileName());
						msg.append(" as it failed to match pattern:");
						msg.append(ftDetail.getId().getLogFilePattern());
						ASCLogger.error(this.getClass(), methodName, msg
								.toString(), true);
					}
				}
			} catch (RESyntaxException reSyntaxException) {
				throw new FileTransferException(
						"Invalid regular expression syntax defined for file name pattern:"
								+ ftDetail, PRC_FT_F_REX_001);
			}
		}
	}

	/**
	 * Validates the request and then carries out the transfer, all transfers
	 * first transfer the files as .incomplete file and then rename the same
	 * 
	 * @param ftDetail
	 * @param fileInfo
	 */
	protected void performTransfer(CfgTaskFtDetails ftDetail,
			ActFileTransferDetails fileInfo) {
		String methodName = "performTransfer";
		String destination = null;
		try {
			destination = this.parseDestination(ftDetail.getTemplateDef(),
					fileInfo);
		} catch (FileTransferException ftException) {
			this.markFailed(fileInfo.getFtId(), UNEVALUATED,
					"Destination couldn't be parsed", ftException);
		}
		ASCLogger.debug(this.getClass(), methodName, new StringBuffer().append(
				"Template:").append(ftDetail.getTemplateDef()).append(
				" parsed to:").append(destination).toString());
		File destFile = new File(destination);

		// If not transferable then just return we would have already marked the
		// request as failed
		if (!this.validateTransferrable(fileInfo.getFile(), destFile, fileInfo.getFtId())) {
			return;
		}
		String incompleteDestFile = this.getInCompleteFile(destination,
				fileInfo.getFtId());
		ASCLogger.debug(this.getClass(), methodName, "IncompleteDestFile is:"
				+ incompleteDestFile);
		// We would have marked the request as failed as we can't get the
		// incomplete file object
		if (ASCUtil.isNullOrEmptyObject(incompleteDestFile)) {
			return;
		}
		this.putFile(fileInfo.getFile(), destination, incompleteDestFile, fileInfo
				.getFtId());
	}

	/**
	 * Parses the destination to handle dynamic parameters
	 * 
	 * @param isdFtTransactionDetails
	 * @param documentManagerVO
	 * @throws InValidVOException
	 */
	public String parseDestination(String destination, ActFileTransferDetails fileInfo) {
		// Parses {$FILENAME} parameter
		destination = this.parseFileName(destination, fileInfo.getFile());
		// Parses Time stamp parameter
		destination = this.parseTimeStamp(destination, fileInfo.getStartTime());
		// Parses {$Prop:[PropertyName]} parameter
		destination = this.parseProperties(destination);
		// Parses {$RProp:[PropertyName]} parameter
		destination = this.parseRequestProperties(destination);
		return destination;
	}

	/**
	 * Parses {$FILENAME} parameter
	 * 
	 * @param destination
	 * @param documentManagerVO
	 * @return
	 */
	private String parseFileName(String destination, File srcFile) {
		if (ASCUtil.isNullOrEmpty(srcFile.getName())) {
			return destination;
		}
		File fileName = new File(srcFile.getName());
		destination = parseFileBaseName(destination, srcFile);
		destination = parseFileExtension(destination, srcFile);
		while (destination.indexOf(FILE_NAME_IDENTIFIER) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(FILE_NAME_IDENTIFIER));
			String lastPart = destination.substring(destination
					.indexOf(FILE_NAME_IDENTIFIER)
					+ FILE_NAME_IDENTIFIER.length());
			destination = new StringBuffer().append(firstPart).append(
					fileName.getName()).append(lastPart).toString();
		}
		return destination;
	}

	/**
	 * Parses {$FILENAME_BASE} parameter
	 * 
	 * @param destination
	 * @param documentManagerVO
	 * @return
	 */
	private String parseFileBaseName(String destination, File srcFile) {
		String filename = new File(srcFile.getName()).getName();
		String baseName = null;

		if (ASCUtil.isNullOrEmpty(filename)) {
			return destination;
		}
		if (filename.indexOf(DOT) != -1) {
			baseName = filename.substring(0, filename.indexOf(DOT));
		}

		while (destination.indexOf(FILE_BASE_NAME_IDENTIFIER) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(FILE_BASE_NAME_IDENTIFIER));
			String lastPart = destination.substring(destination
					.indexOf(FILE_BASE_NAME_IDENTIFIER)
					+ FILE_BASE_NAME_IDENTIFIER.length());
			destination = new StringBuffer().append(firstPart).append(baseName)
					.append(lastPart).toString();
		}
		return destination;
	}

	/**
	 * Parses {$FILENAME_EXT} parameter
	 * 
	 * @param destination
	 * @param documentManagerVO
	 * @return
	 */
	private String parseFileExtension(String destination, File srcFile) {
		String filename = new File(srcFile.getName()).getName();
		String extension = null;

		if (ASCUtil.isNullOrEmpty(filename)) {
			return destination;
		}
		if (filename.indexOf(DOT) != -1) {
			extension = filename
					.substring(filename.indexOf(DOT) + DOT.length());
		}
		while (destination.indexOf(FILE_EXTENSION_IDENTIFIER) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(FILE_EXTENSION_IDENTIFIER));
			String lastPart = destination.substring(destination
					.indexOf(FILE_EXTENSION_IDENTIFIER)
					+ FILE_EXTENSION_IDENTIFIER.length());
			destination = new StringBuffer().append(firstPart)
					.append(extension).append(lastPart).toString();
		}
		return destination;
	}

	/**
	 * Parses Time stamp parameter
	 * 
	 * @param destination
	 * @param fileDate 
	 * @param documentManagerVO
	 * @return
	 * @throws InValidVOException
	 */
	private String parseTimeStamp(String destination, Date fileDate) {
		while (destination.indexOf(TIME_STAMP_IDENTIFIER) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(TIME_STAMP_IDENTIFIER));
			destination = destination.substring(destination
					.indexOf(TIME_STAMP_IDENTIFIER));
			String dtFormat = destination.substring(destination
					.indexOf(TIME_STAMP_IDENTIFIER)
					+ TIME_STAMP_IDENTIFIER.length(), destination
					.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE));
			String endPart = destination.substring(destination
					.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE)
					+ PROPERTY_IDENTIFIER_END_CLAUSE.length());
			SimpleDateFormat formatter = new SimpleDateFormat(dtFormat);
			//formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
			String dtValue = formatter.format(fileDate);
			destination = new StringBuffer().append(firstPart).append(dtValue)
					.append(endPart).toString();
		}
		return destination;
	}

	/**
	 * Parses {$PROPERTY_NAME} parameter
	 * 
	 * @param destination
	 * @param documentManagerVO
	 * @return
	 * @throws InValidVOException
	 */
	private String parseProperties(String destination) {
		while (destination.indexOf(PROPERTY_IDENTIFIER_START_CLAUSE) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(PROPERTY_IDENTIFIER_START_CLAUSE));
			destination = destination.substring(destination
					.indexOf(PROPERTY_IDENTIFIER_START_CLAUSE));
			String lastPart = destination.substring(destination
					.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE)
					+ PROPERTY_IDENTIFIER_END_CLAUSE.length());
			String propertyName = destination.substring(destination
					.indexOf(PROPERTY_IDENTIFIER_START_CLAUSE)
					+ PROPERTY_IDENTIFIER_START_CLAUSE.length(), destination
					.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE));
			if (ASCUtil.isNullOrEmpty(propertyName)) {
				throw new FileTransferException("Property Name not defined",
						PRC_FT_F_PAR_002);
			}
			destination = new StringBuffer().append(firstPart).append(
					(this.getPropertyValue(propertyName)).trim()).append(
					lastPart).toString();
		}
		return destination;
	}

	/**
	 * Returns value for the property name
	 * 
	 * @param propertyName
	 * @return
	 */
	private String getPropertyValue(String propertyName) {
		String value = "";
		if ("Modality".equalsIgnoreCase(propertyName)) {
			value = request.getModality();
		} else if ("ModelType".equalsIgnoreCase(propertyName)) {
			value = request.getModelType();
		} else if ("ProductLine".equalsIgnoreCase(propertyName)) {
			value = request.getProductLine();
		} else if ("Pole".equalsIgnoreCase(propertyName)) {
			value = request.getPole();
		} else if ("SystemId".equalsIgnoreCase(propertyName)) {
			value = request.getSystemId();
		} else if ("IsoCode".equalsIgnoreCase(propertyName)) {
			value = request.getIsoCode();
		} else if ("USN".equalsIgnoreCase(propertyName)) {
			value = request.getUsn();
		} else if ("MLN".equalsIgnoreCase(propertyName)) {
			value = request.getMln();
		} else if ("AddressId".equalsIgnoreCase(propertyName)) {
			value = request.getAddressId().toString();
		} else {
			throw new FileTransferException(
					"Failed to parse property value for property name:"
							+ propertyName, PRC_FT_F_PAR_001);
		}
		if (ASCUtil.isNullOrEmpty(value)) {
			throw new FileTransferException(
					"No property value for property name:" + propertyName,
					PRC_FT_F_PAR_001);
		}
		return value;
	}

	/**
	 * Parses {$RProp:[PropertyName]} tag
	 * 
	 * @param destination
	 * @return
	 */
	private String parseRequestProperties(String destination) {
		while (destination.indexOf(REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE) != -1) {
			String firstPart = destination.substring(0, destination
					.indexOf(REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE));
			destination = destination.substring(destination
					.indexOf(REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE));
			String lastPart = destination.substring(destination
					.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE)
					+ PROPERTY_IDENTIFIER_END_CLAUSE.length());
			String propertyName = destination.substring(destination
					.indexOf(REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE)
					+ REQUEST_PROPERTY_IDENTIFIER_START_CLAUSE.length(),
					destination.indexOf(PROPERTY_IDENTIFIER_END_CLAUSE));
			if (ASCUtil.isNullOrEmpty(propertyName)) {
				throw new FileTransferException(
						"Request Property Name not defined", PRC_FT_F_PAR_002);
			}
			String requestPropertyValue = this
					.getRequestPropertyValue(propertyName);
			if (ASCUtil.isNullOrEmpty(requestPropertyValue)) {
				throw new FileTransferException(
						"No request property value for property name:"
								+ propertyName, PRC_FT_F_PAR_001);
			}
			destination = new StringBuffer().append(firstPart).append(
					(requestPropertyValue).trim()).append(lastPart).toString();
		}
		return destination;
	}

	/**
	 * Does the validation, FileCopy and FTP/SFTP does different validations
	 * 
	 * @param srcFile
	 * @param destFile
	 * @param ftId
	 * @return
	 */
	protected abstract boolean validateTransferrable(File srcFile,
			File destFile, Long ftId);

	/**
	 * Returns incomplete file name For FTP its different as we don't have
	 * access to there remote host
	 * 
	 * @param destFile
	 * @param ftId
	 * @return
	 */
	protected abstract String getInCompleteFile(String destFile, Long ftId);

	/**
	 * Actually transfers the file to remote device
	 * 
	 * @param srcFile
	 * @param destFile
	 * @param incompleteDestFile
	 * @param ftId
	 */
	protected abstract void putFile(File srcFile, String destFile,
			String incompleteDestFile, Long ftId);

	/**
	 * @param fileTransferDAO
	 *            the fileTransferDAO to set
	 */
	public void setFileTransferDAO(IFileTransferDetailsDAO fileTransferDAO) {
		BaseFileTransferTask.fileTransferDAO = fileTransferDAO;
	}

	/**
	 * @param ftDetailsDAO
	 *            the ftDetailsDAO to set
	 */
	public void setFtDetailsDAO(ITaskFTDetailsDAO ftDetailsDAO) {
		BaseFileTransferTask.ftDetailsDAO = ftDetailsDAO;
	}

	/**
	 * @param ftConnDetailsDAO
	 *            the ftConnDetailsDAO to set
	 */
	public void setFtConnDetailsDAO(ITaskFTConnInfoDAO ftConnDetailsDAO) {
		BaseFileTransferTask.ftConnDetailsDAO = ftConnDetailsDAO;
	}

	/**
	 * @param ftRecordDAO
	 *            the ftRecordDAO to set
	 */
	public void setFtRecordDAO(ITaskFtRecordDAO ftRecordDAO) {
		BaseFileTransferTask.ftRecordDAO = ftRecordDAO;
	}

	/**
	 * @param requestDAO
	 *            the requestDAO to set
	 */
	public void setRequestDAO(IRequestDAO requestDAO) {
		BaseFileTransferTask.requestDAO = requestDAO;
	}

	/**
	 * @param requestPropertiesDAO
	 *            the requestPropertiesDAO to set
	 */
	public void setRequestPropertiesDAO(
			IRequestPropertiesDAO requestPropertiesDAO) {
		BaseFileTransferTask.requestPropertiesDAO = requestPropertiesDAO;
	}
}
